#include "bindings/bindings_linear_animation_instance.h"
#include "helpers/general.h"
#include "refs.h"
#include "rive/animation/linear_animation_instance.hpp"
#include "rive/animation/loop.hpp"
#include <cstdint>
#include <napi/native_api.h>

namespace ohos_rive {

static rive::LinearAnimationInstance *GetLinearAnimationInstance(napi_env env, napi_value arg)
{
    intptr_t instancePtr = 0;
    if (napi_get_value_int64(env, arg, &instancePtr) != napi_ok) {
        LOGE("Animation Instance pointer get failed.");
        return nullptr;
    }
    return reinterpret_cast<rive::LinearAnimationInstance *>(instancePtr);
}

napi_value Advance(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2];

    auto status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < 2) {
        LOGE("Invalid arguments, actual number of arguments is %ld", argc);
        return nullptr;
    }

    auto animationInstance = GetLinearAnimationInstance(env, args[0]);
    if (animationInstance == nullptr) {
        return nullptr;
    }

    double elapsedTime = 0;
    status = napi_get_value_double(env, args[1], &elapsedTime);
    if (status != napi_ok) {
        LOGE("Elapsed time get failed.");
        return nullptr;
    }

    animationInstance->advance(static_cast<float>(elapsedTime));
    bool didLoop = animationInstance->didLoop();

    napi_value loopValue = nullptr;
    if (didLoop) {
        rive::Loop loopType = animationInstance->loop();
        switch (loopType) {
            case rive::Loop::oneShot:
                loopValue = GetOneShotLoopField(env);
                break;
            case rive::Loop::loop:
                loopValue = GetLoopLoopField(env);
                break;
            case rive::Loop::pingPong:
                loopValue = GetPingPongLoopField(env);
                break;
            default:
                loopValue = GetNoneLoopField(env);
                break;
        }
    }
    return loopValue;
}

napi_value AdvanceAndGetResult(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2];

    auto status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < 2) {
        LOGE("Invalid arguments, actual number of arguments is %ld", argc);
        return nullptr;
    }

    auto animationInstance = GetLinearAnimationInstance(env, args[0]);
    if (animationInstance == nullptr) {
        return nullptr;
    }

    double elapsedTime = 0;
    status = napi_get_value_double(env, args[1], &elapsedTime);
    if (status != napi_ok) {
        LOGE("Elapsed time get failed.");
        return nullptr;
    }

    bool keepGoing = animationInstance->advance(static_cast<float>(elapsedTime));
    bool didLoop = animationInstance->didLoop();

    napi_value advanceResultValue = nullptr;

    if (didLoop) {
        rive::Loop loopType = animationInstance->loop();
        switch (loopType) {
            case rive::Loop::oneShot:
                advanceResultValue = GetAdvanceResultOneShotField(env);
                break;
            case rive::Loop::loop:
                advanceResultValue = GetAdvanceResultLoopField(env);
                break;
            case rive::Loop::pingPong:
                advanceResultValue = GetAdvanceResultPingPongField(env);
                break;
            default:
                // This should not happen: if we looped, we should get a
                // loop result.
                assert(false); // N.B. asserts are compiled out in release mode
                advanceResultValue = GetAdvanceResultNoneField(env);
                break;
        }
    } else if (keepGoing) {
        advanceResultValue = GetAdvanceResultAdvancedField(env);
    } else {
        advanceResultValue = GetAdvanceResultNoneField(env);
    }

    return advanceResultValue;
}

napi_value Apply(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2];

    napi_value undefined;
    napi_get_undefined(env, &undefined);

    auto status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < 2) {
        LOGE("Invalid arguments, actual number of arguments is %ld", argc);
        return undefined;
    }

    auto animationInstance = GetLinearAnimationInstance(env, args[0]);
    if (animationInstance == nullptr) {
        return undefined;
    }

    double mix = 0;
    status = napi_get_value_double(env, args[1], &mix);
    if (status != napi_ok) {
        LOGE("mix get failed.");
        return undefined;
    }
    animationInstance->apply(static_cast<float>(mix));

    LOGI("Apply mix: %{public}f success.", mix);
    return undefined;
}

napi_value GetTime(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1];

    auto status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < 1) {
        LOGE("Invalid arguments, actual number of arguments is %ld", argc);
        return nullptr;
    }

    auto animationInstance = GetLinearAnimationInstance(env, args[0]);
    if (animationInstance == nullptr) {
        return nullptr;
    }

    napi_value time;
    status = napi_create_double(env, animationInstance->time(), &time);
    if (status != napi_ok) {
        LOGE("Create time napi value failed.");
        return nullptr;
    }
    return time;
}

napi_value SetTime(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2];

    napi_value undefined;
    napi_get_undefined(env, &undefined);

    auto status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < 2) {
        LOGE("Invalid arguments, actual number of arguments is %ld", argc);
        return undefined;
    }

    auto animationInstance = GetLinearAnimationInstance(env, args[0]);
    if (animationInstance == nullptr) {
        return undefined;
    }

    double time = 0;
    status = napi_get_value_double(env, args[1], &time);
    if (status != napi_ok) {
        LOGE("Time get failed.");
        return undefined;
    }

    animationInstance->time(time);
    return undefined;
}

napi_value SetDirection(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2];

    napi_value undefined;
    napi_get_undefined(env, &undefined);

    auto status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < 2) {
        LOGE("Invalid arguments, actual number of arguments is %ld", argc);
        return undefined;
    }

    auto animationInstance = GetLinearAnimationInstance(env, args[0]);
    if (animationInstance == nullptr) {
        return undefined;
    }

    int direction;
    status = napi_get_value_int32(env, args[1], &direction);
    if (status != napi_ok) {
        LOGE("Get direction error.");
        return undefined;
    }
    animationInstance->direction(direction);
    return undefined;
}

napi_value GetDirection(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1];

    auto status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < 1) {
        LOGE("Invalid arguments, actual number of arguments is %ld", argc);
        return nullptr;
    }

    auto animationInstance = GetLinearAnimationInstance(env, args[0]);
    if (animationInstance == nullptr) {
        return nullptr;
    }

    napi_value direction;
    status = napi_create_int32(env, animationInstance->direction(), &direction);
    if (status != napi_ok) {
        LOGE("Create direction napi value failed.");
        return nullptr;
    }
    return direction;
}

napi_value GetLoop(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1];

    auto status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < 1) {
        LOGE("Invalid arguments, actual number of arguments is %ld", argc);
        return nullptr;
    }

    auto animationInstance = GetLinearAnimationInstance(env, args[0]);
    if (animationInstance == nullptr) {
        return nullptr;
    }

    napi_value loopValue;
    status = napi_create_uint32(env, (unsigned int)animationInstance->loop(), &loopValue);
    if (status != napi_ok) {
        LOGE("Create loop napi value failed.");
        return nullptr;
    }
    return loopValue;
}

napi_value SetLoop(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2];

    napi_value undefined;
    napi_get_undefined(env, &undefined);

    auto status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < 2) {
        LOGE("Invalid arguments, actual number of arguments is %ld", argc);
        return undefined;
    }

    auto animationInstance = GetLinearAnimationInstance(env, args[0]);
    if (animationInstance == nullptr) {
        return undefined;
    }

    int loopType;
    status = napi_get_value_int32(env, args[1], &loopType);
    if (status != napi_ok) {
        LOGE("Get loop value failed.");
        return nullptr;
    }

    animationInstance->loopValue(loopType);
    return undefined;
}

// ANIMATION
napi_value Name(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1];

    auto status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < 1) {
        LOGE("Invalid arguments, actual number of arguments is %ld", argc);
        return nullptr;
    }

    auto animationInstance = GetLinearAnimationInstance(env, args[0]);
    if (animationInstance == nullptr) {
        return nullptr;
    }

    auto name = animationInstance->animation()->name();
    napi_value nameValue;
    status = napi_create_string_utf8(env, name.c_str(), name.length(), &nameValue);
    if (status != napi_ok) {
        LOGE("Create name napi value failed.");
        return nullptr;
    }
    return nameValue;
}

napi_value Duration(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1];

    auto status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < 1) {
        LOGE("Invalid arguments, actual number of arguments is %ld", argc);
        return nullptr;
    }

    auto animationInstance = GetLinearAnimationInstance(env, args[0]);
    if (animationInstance == nullptr) {
        return nullptr;
    }

    napi_value duration;
    status = napi_create_uint32(env, animationInstance->animation()->duration(), &duration);
    if (status != napi_ok) {
        LOGE("Create duration napi value failed.");
        return nullptr;
    }
    return duration;
}

napi_value Fps(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1];

    auto status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < 1) {
        LOGE("Invalid arguments, actual number of arguments is %ld", argc);
        return nullptr;
    }

    auto animationInstance = GetLinearAnimationInstance(env, args[0]);
    if (animationInstance == nullptr) {
        return nullptr;
    }

    napi_value fps;
    status = napi_create_uint32(env, animationInstance->animation()->fps(), &fps);
    if (status != napi_ok) {
        LOGE("Create fps napi value failed.");
        return nullptr;
    }
    return fps;
}

napi_value WorkStart(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1];

    auto status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < 1) {
        LOGE("Invalid arguments, actual number of arguments is %ld", argc);
        return nullptr;
    }

    auto animationInstance = GetLinearAnimationInstance(env, args[0]);
    if (animationInstance == nullptr) {
        return nullptr;
    }

    napi_value workStart;
    status = napi_create_uint32(env, animationInstance->animation()->workStart(), &workStart);
    if (status != napi_ok) {
        LOGE("Create workStart napi value failed.");
        return nullptr;
    }

    LOGI("Get workStart value success.");
    return workStart;
}

napi_value WorkEnd(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1];

    auto status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < 1) {
        LOGE("Invalid arguments, actual number of arguments is %ld", argc);
        return nullptr;
    }

    auto animationInstance = GetLinearAnimationInstance(env, args[0]);
    if (animationInstance == nullptr) {
        return nullptr;
    }

    napi_value workEnd;
    status = napi_create_uint32(env, animationInstance->animation()->workEnd(), &workEnd);
    if (status != napi_ok) {
        LOGE("Create workEnd napi value failed.");
        return nullptr;
    }

    LOGI("Get workEnd value success.");
    return workEnd;
}

napi_value Delete(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1];

    napi_value undefined;
    napi_get_undefined(env, &undefined);

    auto status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < 1) {
        LOGE("Invalid arguments, actual number of arguments is %ld", argc);
        return undefined;
    }

    auto animationInstance = GetLinearAnimationInstance(env, args[0]);
    if (animationInstance == nullptr) {
        return undefined;
    }
    delete animationInstance;

    LOGI("Delete linear animation instance success.");
    return undefined;
}

} // namespace ohos_rive
